#include "ngx_ngim.h"
#include "ngx_ngim_util.h"
#include "ngx_ngim_curl.h"


/*static method */
static u_char   *ngx_ngim_log_error(ngx_log_t *log, u_char *buf, size_t len);
static void      ngx_ngim_close_session_handler(ngx_event_t *e);
static void      ngx_ngim_detect_protocol(ngx_ngim_session_t *s);
static void      ngx_ngim_do_detect_protocol(ngx_event_t *rev);


ngx_ngim_session_t *
ngx_ngim_init_session(ngx_connection_t *c, ngx_ngim_addr_conf_t *addr_conf)
{
  ngx_ngim_session_t             *s;
  ngx_ngim_core_srv_conf_t       *cscf;
  ngx_ngim_log_ctx_t             *ctx;

  s = ngx_pcalloc(c->pool, sizeof(ngx_ngim_session_t));
  if (s == NULL) {
      ngx_ngim_close_connection(c);
      return NULL;
  }

  s->main_conf = addr_conf->ctx->main_conf;
  s->srv_conf = addr_conf->ctx->srv_conf;

  s->addr_text = &addr_conf->addr_text;

  c->data = s;
  s->connection = c;
  s->pool       = c->pool;

  ctx = ngx_palloc(c->pool, sizeof(ngx_ngim_log_ctx_t));
  if (ctx == NULL) {
      ngx_ngim_close_connection(c);
      return NULL;
  }

  ctx->client = &c->addr_text;
  ctx->session = s;

  c->log->connection = c->number;
  c->log->handler = ngx_ngim_log_error;
  c->log->data = ctx;
  c->log->action = NULL;

  c->log_error = NGX_ERROR_INFO;

  s->ctx = ngx_pcalloc(c->pool, sizeof(void *) * ngx_ngim_max_module);
  if (s->ctx == NULL) {
      ngx_ngim_close_connection(c);
      return NULL;
  }

  cscf = ngx_ngim_get_module_srv_conf(s, ngx_ngim_core_module);

  s->epoch = ngx_current_msec;
  s->timeout = cscf->timeout;
  s->stage = NGX_NGIM_SESSION_INIT;

  ngx_ngim_run_session(s);
  return s;
}


void   ngx_ngim_run_session(ngx_ngim_session_t *s) //cycle
{
  ngx_uint_t  save_stage;

  switch(s->stage){
     case NGX_NGIM_SESSION_INIT:
          if (ngx_ngim_fire_event(s,NGX_NGIM_SESSION_INIT,NULL,NULL) != NGX_OK) {
              ngx_ngim_finalize_session(s);
              return;
          }
          s->stage++;
 
     case NGX_NGIM_SESSION_READ_REQUEST:
          ngx_ngim_detect_protocol(s);
          break;

     case NGX_NGIM_SESSION_PREPARE_REQUEST:
          save_stage = s->stage;          
          if (ngx_ngim_fire_event(s, s->stage,NULL,NULL) != NGX_OK) {
              ngx_ngim_finalize_session(s);
              return;
          }
          if(s->stage == save_stage)
             s->stage++;

     case NGX_NGIM_SESSION_DO_REQUEST:
          save_stage = s->stage;          
          if (ngx_ngim_fire_event(s, s->stage,NULL,NULL) != NGX_OK) {
              ngx_ngim_finalize_session(s);
              return;
          }
          if(s->stage == save_stage)
             s->stage++;
       
          if(ngx_ngim_request_is_async(s,-1) == NGX_OK){
             s->async = 1;
          }else{
             break;
          }

     case NGX_NGIM_SESSION_PREPARE_RESPONSE:
          save_stage = s->stage;          
          if (ngx_ngim_fire_event(s, s->stage,NULL,NULL) != NGX_OK) {
              ngx_ngim_finalize_session(s);
              return;
          }
          if(s->stage == save_stage)
             s->stage++;

     case NGX_NGIM_SESSION_DO_RESPONSE:
          if (ngx_ngim_fire_event(s, NGX_NGIM_SESSION_DO_RESPONSE,NULL,NULL) != NGX_OK) {
              ngx_ngim_finalize_session(s);
              return;
          }
          break;

     case NGX_NGIM_SESSION_CLOSE_CONNECTION:
          save_stage = s->stage;          
          if (ngx_ngim_fire_event(s, s->stage,NULL,NULL) != NGX_OK) {
              return;
          }
          if(s->stage == save_stage)
             s->stage++;
          break;


     default:
          break;
  }
}

void
ngx_ngim_finalize_async_session(ngx_ngim_session_t *s)
{
    ngx_connection_t   *c;

    c = s->connection;

    ngx_log_debug0(NGX_LOG_DEBUG_NGIM, c->log, 0, 
                   "finalize async session");
   
    if( s->async &&  c->read->closed  == 1 &&
                     c->write->closed == 1 ){
        if(s->root){
           cJSON_Delete(s->root);
           s->root = NULL;
        }

        if(s->buff){
           free(s->buff);
           s->buff = NULL;
        }

        s->stage = NGX_NGIM_SESSION_CLOSE_CONNECTION;
        ngx_ngim_run_session(s);

        ngx_ngim_free_connection(c);
    }
}


void
ngx_ngim_finalize_session(ngx_ngim_session_t *s)
{
    ngx_event_t        *e;
    ngx_connection_t   *c;

    c = s->connection;

    ngx_log_debug0(NGX_LOG_DEBUG_NGIM, c->log, 0, "finalize session");

    c->destroyed = 1;
    e = &s->close;
    e->data = s;
    e->handler = ngx_ngim_close_session_handler;
    e->log = c->log;

    ngx_post_event(e, &ngx_posted_events);
}



void      
ngx_ngim_close_session(ngx_ngim_session_t *s)
{
   ngx_connection_t  *c;
   
   c   =  s->connection;

   if(s->root){
      cJSON_Delete(s->root);
      s->root = NULL;
   }

   if(s->buff){
      free(s->buff);
      s->buff = NULL;
   }

   s->stage = NGX_NGIM_SESSION_CLOSE_CONNECTION;
   ngx_ngim_run_session(s);

   ngx_ngim_close_connection(c);
}

static u_char *
ngx_ngim_log_error(ngx_log_t *log, u_char *buf, size_t len)
{
    u_char              *p;
    ngx_ngim_session_t  *s;
    ngx_ngim_log_ctx_t  *ctx;

    if (log->action) {
        p = ngx_snprintf(buf, len, " while %s", log->action);
        len -= p - buf;
        buf = p;
    }

    ctx = log->data;

    p = ngx_snprintf(buf, len, ", client: %V", ctx->client);
    len -= p - buf;
    buf = p;

    s = ctx->session;

    if (s == NULL) {
        return p;
    }

    p = ngx_snprintf(buf, len, ", server: %V", s->addr_text);
    len -= p - buf;
    buf = p;

    return p;
}


static void
ngx_ngim_close_session_handler(ngx_event_t *e)
{
    ngx_ngim_session_t                 *s;
    ngx_connection_t                   *c;

    s = e->data;
    c = s->connection;

    ngx_log_debug0(NGX_LOG_DEBUG_NGIM, c->log, 0, "close session");

    ngx_ngim_close_session(s);
}


static void      
ngx_ngim_detect_protocol(ngx_ngim_session_t *s)
{
    ngx_connection_t     *c;
    ngx_event_t          *rev;

    c = s->connection;
    rev = c->read;
    
    rev->handler = ngx_ngim_do_detect_protocol;
    c->write->handler = ngx_ngim_empty_handler;

    ngx_log_debug(NGX_LOG_DEBUG_NGIM, s->connection->log, 0,
            "ngx_ngim_detect_protocol %i",rev->ready);

    if (rev->ready) {

        rev->handler(rev);
        return;
    }

    ngx_add_timer(rev, s->timeout);

    if (ngx_handle_read_event(rev, 0) != NGX_OK) {
        ngx_ngim_finalize_session(s);
        return;
    }
}


static void
ngx_ngim_do_detect_protocol(ngx_event_t *rev)
{
    ssize_t                     n;
    ngx_connection_t           *c;
    ngx_ngim_session_t         *s;
    u_char                      buf[1] = {'\0'};

    c = rev->data;
    s = c->data;

    if (c->destroyed) {
        return;
    }

    if (rev->timedout) {
        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
                "ngx_ngim_do_detect_protocol: client timed out");
        c->timedout = 1;
        ngx_ngim_finalize_session(s);
        return;
    }

    if (rev->timer_set) {
        ngx_del_timer(rev);
    }

    n = ngx_ngim_recv_peek(c,buf,1);

    if (n == NGX_ERROR || n == 0) {
        ngx_ngim_finalize_session(s);
        return;
    }

    if (n == NGX_AGAIN) {
       ngx_add_timer(rev, s->timeout);
       if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
           ngx_ngim_finalize_session(s);
       }
       return;
    }

    ngx_log_debug1(NGX_LOG_DEBUG_NGIM, s->connection->log, 0,
                   "ngx_ngim_do_detect_protocol Method:%c",buf[0]);

    if (ngx_ngim_fire_event(s, NGX_NGIM_SESSION_READ_REQUEST,NULL,buf) != NGX_OK) {
        ngx_ngim_finalize_session(s);
        return;
    }

    if(s->stage == NGX_NGIM_SESSION_READ_REQUEST)
       s->stage++;

    return;
}
